''' .map 文件解析器 '''

import re 
import os
import json
from tileimage import image_to_tilemap, get_nearest_color, hex_to_tuple
from functools import lru_cache

DEBUG = False

TILES = [
	"IMPASSABLE",			# 空
	"ROAD",					# 道路
	"ROCKY",				# 岩石
	"DIRT",
	"SAVANNA",
	"GRASS",				# 浅色绿草
	"FOREST",				# 森林
	"MARSH",
	"WEB",
	"WOODFLOOR",
	"CARPET",
	"CHECKER",				# 棋盘
	# CAVES
	"CAVE",
	"FUNGUS",				# 蘑菇林
	"SINKHOLE",
	"UNDERROCK",
	"MUD",
	"BRICK",
	"BRICK_GLOW",
	"TILES",
	"TILES_GLOW",
	"TRIM",
	"TRIM_GLOW",
	"FUNGUSRED",
	"FUNGUSGREEN",
	"DEEPRAINFOREST",  	
	"FOUNDATION", 		
	"COBBLEROAD", 		
	"LAWN", 				
	"DECIDUOUS",
	"DESERT_DIRT",
	"GASJUNGLE",			
	"RAINFOREST", 		
	"INTERIOR", 				 
	"SNAKESKIN", 
	"FIELDS", 				 
	"SUBURB", 				 
	"PIGRUINS", 			
	"PIGRUINS_NOCANOPY", 
	"BATTLEGROUND", 		
	"PLAINS",			
	"PAINTED",			
	# SHIPWRECKED
	"VOLCANO_ROCK",
	"MEADOW",
	"BEACH",
	"JUNGLE",
	"SWAMP",
	"FLOOD",
	"VOLCANO",
	"VOLCANO_LAVA",
	"ASH",
	"MANGROVE",
	"MAGMAFIELD",
	"TIDALMARSH",
	"OCEAN_SHALLOW",
	"OCEAN_MEDIUM",
	"OCEAN_DEEP",
	"OCEAN_SHORE",
	"OCEAN_CORAL",
	"OCEAN_CORAL_SHORE",
	"MANGROVE_SHORE",
	"OCEAN_SHIPGRAVEYARD",
	"OCEAN_SHIPGRAVEYARD_SHORE",
	"LILYPOND",							
	"BEARDRUG",							
	"DEEPRAINFOREST_NOCANOPY",			
]

def dprint(*args):
	if DEBUG and args:
		print("[DEBUG]   ", *args)

def parse_comment(s):
	m = re.match(r"([^#]*)(#.*)*", s.strip())
	return m.group(1)

def parse_field(s):
	m = re.match(r"\[(.+)\]", s.strip())
	if m is None:
		return False, ""
	elif m.group(1) not in ["tiledef", "tilemap", "objects", "tileimage"]:
		raise TypeError(m.group(1))
		return False, ""
	else:
		return True, m.group(1)

def frange(start, stop = None, step = None):
	if step is None:
		step = start < stop and 1 or -1

	if stop is None:
		stop = start
		start = 0

	if stop < start:
		while stop <= start:
			yield start
			start += step
		return
	elif stop > start:
		while stop >= start:
			yield start
			start += step
		return
	else:
		return

def parse_tile_file(path):
	import json 
	import base64
	from io import BytesIO
	from PIL import Image

	data = json.load(open(path))
	data = json.loads(data["piskel"]["layers"][0])
	data = data["chunks"][0]["base64PNG"]

	data = base64.b64decode(data.split(",")[-1])
	image = BytesIO(data)
	image = Image.open(image)

	image = image.crop(image.getbbox())
	pixel = image.load()
	w, h  = image.size 
	data  = []
	for y in range(h):
		temp = []
		for x in range(w):
			if pixel[x, y][-1]:
				temp.append("+")
			else:
				temp.append(".")

		data.append(temp)

	return data

def parse_tile_file(path):
	return image_to_tilemap(path, 4)

def parse_sequence(s, centerpos: int):
	num = s.count("~")
	temp = s.split("~")
	if num == 0:
		return [parse_pos(s, centerpos)] 
	elif num <= 2:
		return list(frange(
			parse_pos(temp[0], centerpos), 
			parse_pos(temp[1], centerpos),
			parse_pos(temp[2], centerpos) if num == 2 else None))
	else:
		raise RuntimeError("Too many ~: " + s)

def parse_pos(s, centerpos):
	if s[0] == "$":
		# absolute
		return int(s[1:])
	elif s[0] in "+-":
		# relative
		return float(s) + centerpos
	else:
		print("Warning: Flag implicit: " + s)
		return parse_pos("+" + s, centerpos)

def parse_object(s, centerpos):
	temp = list(filter(None, s.strip().split("\t")))
	item = temp[0]
	x, y = temp[1].split(",")
	p = len(temp)==3 and temp[2] or None
	if item.find("[") > 0:
		num = item[item.find("[")+1:-1]
		if not num:
			num = "DYNAMIC"
		else:
			num = int(num)
		item = item[0:item.find("[")]
	else:
		num = 1

	sx = parse_sequence(x, centerpos)
	sy = parse_sequence(y, centerpos)

	if num == "DYNAMIC":
		num = len(sx)*len(sy)
	else:
		assert(num == len(sx)*len(sy))

	print("[parser] %d %s(s)" % (num, item))
	array = []

	for x in sx:
		for y in sy:
			array.append([x, y, p])

	return item, array


def parse(file):
	if file is str:
		content = file.split("\n")
	else:
		content = list(file)

	print("[parser] File loaded, %d line(s)." % len(content))

	field = None
	data = {}
	tilemap = []
	tiledef = []
	objects = []

	for i, line in enumerate(content):
		line = parse_comment(line)
		if not line:
			continue

		dprint(line)

		suc, f = parse_field(line)
		if suc:
			field = f 
			container = {}
			data[field] = container
			continue

		if field == "tiledef":
			# tilevar TAB tileid
			# *\t0
			l = list(filter(None, line.split("\t")))
			if len(l) != 2:
				raise RuntimeError("Line: %d" % (i+1))

			container[l[0]] = int(l[1])

		elif field == "tilemap":
			# ....***.....
			
			container[len(container)] = list(line)

		elif field == "tileimage":
			l = list(filter(None, line.split("\t")))
			if len(l) != 2:
				raise RuntimeError("Line: %d" % (i+1))

			container[l[0]] = l[1]

		elif field == "objects":
			# item TAB posx,posy
			# twigs\t+1,-1
			
			container[line] = True

	return data

def parse_file(path):
	return parse(open(path))

def get_rect(tilemap):
	left, top = 1e10, 1e10
	right, bottom = 0, 0

	if isinstance(tilemap, list):
		tilemap = {i:v for i,v in enumerate(tilemap)}
	for y, line in tilemap.items():
		for x, i in enumerate(line):
			if i != ".":
				left = min(left, x)
				right = max(right, x)
				top  = min(top, y)
				bottom = max(bottom, y)
	return left, top, right, bottom

def layout(data):
	result = {
		"version": 			"1.1",
		"luaversion":		"5.1",
		"orientation":		"orthogonal",
		"width":			0,
		"height":			0,
		"tilewidth": 		64,
		"tileheight":		64,
		"properties":		{},

		"layers": [
			{
				"type":			"tilelayer",
				"name":			"BG_TILES",
				"width":		0,
				"height":		0,
				"data":			[],
			},
			{
				"type":	 		"objectgroup",
				"name":	 		"FG_OBJECTS",
				"objects": 		[],
			},
		],
	}

	# tiles
	def tileid(s):
		if s in data["tiledef"]:
			return data["tiledef"][s]
		else:
			raise RuntimeError("Tiledef not found: " + s)

	useimage = False
	if data.get("tileimage"):
		path = data["tileimage"]["file"]
		useimage = True
		data["tilemap"] = parse_tile_file(path)

	d = result["layers"][0]["data"]
	w = len(data["tilemap"][0])
	h = len(data["tilemap"])
	if w != h:
		print("Warning: w != h")

	orisize = max(w, h)
	print("[parser] Origin size = %d" % orisize)
	border = data["tiledef"].get("border", 0)
	size = data["tiledef"].get("size", orisize) + border*2
	empty = data["tiledef"].get("empty", 0)
	if (orisize + size) % 2:
		size += 1
	print("[parser] Tilemap size = %d" % size)


	result["layers"][0]["width"] = size
	result["layers"][0]["height"] = size
	result["width"] = size
	result["height"]= size

	left, top, right, bottom = get_rect(data["tilemap"])
	width = right - left
	height = bottom - top
	# print(left, top, right, bottom)
	xoffset = (size - width) // 2 - left
	yoffset = (size - height)// 2 - top

	if useimage:
		colorset = {(0,0,0,0): 1}
		for k,v in data["tileimage"].items():
			if k != "file":
				colorset[hex_to_tuple(k)] = int(v)
		getter = get_nearest_color(colorset.keys())

	for y in range(size):
		for x in range(size):
			samplerx = x - xoffset
			samplery = y - yoffset
			if 0 <= samplerx < w and 0 <= samplery < h:
				if useimage:
					# print(samplery, samplerx)
					# print(data["tilemap"][samplery][samplerx])
					c = getter(data["tilemap"][samplery][samplerx])
					d.append(colorset[c])
				else:
					d.append(tileid(data["tilemap"][samplery][samplerx]))
			elif x < border or x >= size - border or y < border or y >= size - border:
				d.append(empty)
			else:
				d.append(empty)

	# print(d, sum(d))

	centerpos = size/2 * 4

	# objects
	def add_object(prefab, x, y, width, height, properties):
		result["layers"][1]["objects"].append({
			"name":			"",
			"type":			prefab,
			"x":			x*16,
			"y":			y*16,
			"width":		width,
			"height":		height,
			"properties":	properties,
			})

	for line in data["objects"]:
		item, array = parse_object(line, centerpos)
		for pos in array:
			add_object(item, pos[0], pos[1], 0, 0, json.loads(pos[2] or "[]"))

	return result

if __name__ == '__main__':
	data = parse_file("template.map")
	layout(data)
